using SharpGraphT.Sharp.Sharp.Util;

namespace SharpGraphT.Graph.Builder;

public abstract class AbstractGraphBuilder<TV, TE, TG, TB>
    where TG : IGraph<TV, TE>
    where TB : AbstractGraphBuilder<TV, TE, TG, TB>
    where TE : class, new() {
    protected readonly TG graph;

    public AbstractGraphBuilder(TG baseGraph) => graph = baseGraph;

    protected abstract TB Self { get; }

    public TB AddVertex(TV vertex) {
        Objects.RequireNonDefault(vertex,
            "Vertex of a default value should not be added");

        graph.AddVertex(vertex);
        return Self;
    }

    public TB AddVertices(params TV[] vertices) {
        foreach (var vertex in vertices) {
            AddVertex(vertex);
        }

        return Self;
    }

    public TB AddEdge(TV source, TV target) {
        graph.AddEdgeWithVertices(source, target);
        return Self;
    }

    public TB AddEdge(TV source, TV target, TE edge) {
        AddVertex(source);
        AddVertex(target);
        graph.AddEdge(source, target, edge);
        return Self;
    }

    public TB AddEdgeChain(TV first, TV second, params TV[] rest) {
        AddEdge(first, second);
        var last = second;
        foreach (var vertex in rest) {
            AddEdge(last, vertex);
            last = vertex;
        }

        return Self;
    }

    public TB AddGraph(IGraph<TV, TE> sourceGraph) {
        graph.AddGraph(sourceGraph);
        return Self;
    }

    public TB RemoveVertex(TV vertex) {
        graph.RemoveVertex(vertex);
        return Self;
    }

    public TB RemoveVertices(params TV[] vertices) {
        foreach (var vertex in vertices) {
            RemoveVertex(vertex);
        }

        return Self;
    }

    public TB RemoveEdge(TV source, TV target) {
        graph.RemoveEdge(source, target);
        return Self;
    }

    public TB RemoveEdge(TE edge) {
        graph.RemoveEdge(edge);
        return Self;
    }

    public TB AddEdge(TV source, TV target, double weight) {
        graph.AddEdgeWithVertices(source, target, weight);
        return Self;
    }

    public TB AddEdge(TV source, TV target, TE edge, double weight) {
        AddEdge(source, target, edge);
        graph.SetEdgeWeight(edge, weight);
        return Self;
    }

    public TG Build() => graph;

    public IGraph<TV, TE> BuildAsUnmodifiable() =>
        new AsUnmodifiableGraph<TV, TE>(graph);
}